import wormbane.config.keys as keys
from wormbane.model.thing import Thing

import curses, sys

def selection_dialog(win, items, **kwargs):
    dialog = SelectionDialog(win, items, **kwargs)
    dialog()
    return dialog._selection

def count(obj):
    return obj.count if isinstance(obj, Thing) else 1

def describe(obj):
    desc = str(obj)
    if isinstance(obj, Thing):
        if obj.is_item and obj.wielded:
            desc += ' (wielded)'
        if obj.is_armor and obj.worn:
            desc += ' (worn)'
    return desc

class SelectionDialog(object):

    def __init__(self, win, items, **kwargs):
        self._win = win
        self._items = items
        self._prompt = kwargs.get('prompt', 'Select')
        self._describe = kwargs.get('describe', describe)
        self._selection = {}
        self._pattern = TextField()
        self._matches = []
        self._i = 0
        self._focus = 'pattern'
        self._auto_selection = set()
        self._done = False
        self._count = kwargs.get('count', count)

    def __call__(self):
        while not self._done:
            self._update_matches()
            self._clear_window()
            self._display_pattern()
            self._display_matches()
            self._display_buttons()
            self._move_cursor()
            key = self._win.getch()
            self._handle_input(key)
        self._clear_window()
        
    def _clear_window(self):
        self._win.clear()

    def _update_matches(self):
        height, width = self._win.getmaxyx()
        s = self._pattern.text.lower()
        self._matches = [item for item in self._items
                         if (self._describe(item).lower().find(s) != -1)]
        self._matches = self._matches[:height - 4]
        if self._focus == 'pattern':
            self._auto_selection = set(self._matches)
        elif self._focus == 'matches':
            self._auto_selection = set([self._matches[self._i]])
        else:
            self._auto_selection.clear()

    def _display_pattern(self):
        height, width = self._win.getmaxyx()
        self._pattern.focus = (self._focus == 'pattern')
        s = '%s %s' % (self._prompt, self._pattern)
        s = s[:width - 1]
        self._win.addstr(0, 0, s)

    def _display_matches(self):
        height, width = self._win.getmaxyx()
        for j, item in zip(xrange(0, sys.maxint), self._matches):
            check = ' '
            if item in self._selection:
                if self._selection[item] < self._count(item):
                    check = '#'
                else:
                    check = '*'
            elif item in self._auto_selection:
                check = '@'
            self._addstr(j + 2, 2, '[%s]' % check)
            desc = self._describe(item)
            k = desc.lower().find(self._pattern.text.lower())
            desc = desc[:width - 5]
            self._addstr(j + 2, 6, desc[:k])
            self._addstr(desc[k:k + len(self._pattern.text)],
                             curses.A_STANDOUT)
            self._addstr(desc[k + len(self._pattern.text):])

    def _display_buttons(self):
        height, width = self._win.getmaxyx()
        self._addstr(len(self._matches) + 3, 0, '[OK] [Cancel]')

    def _move_cursor(self):
        if self._focus == 'pattern':
            x, y = len(self._prompt) + 1 + self._pattern.cursor, 0
        elif self._focus == 'matches':
            x, y = 3, self._i + 2
        elif self._focus == 'ok':
            x, y = 1, len(self._matches) + 3
        else:
            x, y = 6, len(self._matches) + 3
        try:
            self._win.move(y, x)
        except curses.error, e:
            pass

    def _handle_input(self, key):
        if key in (ord('\t'), curses.KEY_BTAB):
            order = ['pattern', 'matches', 'ok', 'cancel']
            if not self._matches:
                order.remove('matches')
            offset = (1 if key == ord('\t') else -1)
            self._focus = order[(order.index(self._focus) + offset)
                                % len(order)]
        elif (key == keys.enter and self._focus != 'cancel'
              or self._focus == 'ok' and key == ord(' ')):
            for item in self._auto_selection:
                if item not in self._selection:
                    self._selection[item] = self._count(item)
            self._done = True
        elif (key == keys.escape
              or self._focus == 'cancel' and key in (ord(' '), keys.enter)):
            self._selection = None
            self._done = True
        elif self._focus == 'pattern':
            if curses.ascii.isprint(key):
                self._pattern.text += chr(key)
            elif key == curses.KEY_BACKSPACE:
                self._pattern.text = self._pattern.text[:-1]
        elif self._focus == 'matches':
            if key in (curses.KEY_LEFT, ord('-')):
                item = self._matches[self._i]
                if item in self._selection:
                    self._selection[item] -= 1
                    if self._selection[item] == 0:
                        del self._selection[item]
            elif key in (curses.KEY_RIGHT, ord('+')):
                item = self._matches[self._i]
                count = self._selection.get(item, 0)
                count = min(count + 1, self._count(item))
                self._selection[item] = count
            elif key in (curses.KEY_UP, curses.KEY_DOWN):
                offset = (-1 if key == curses.KEY_UP else 1)
                self._i = min(max(0, self._i + offset), len(self._matches) - 1)
            elif key == ord(' '):
                item = self._matches[self._i]
                if item in self._selection:
                    del self._selection[item]
                else:
                    self._selection[item] = self._count(item)

    def _addstr(self, *args, **kwargs):
        try:
            self._win.addstr(*args, **kwargs)
        except curses.error:
            pass


class TextField(object):

    def __init__(self, text = '', width = 20):
        self.text = text
        self.width = width
        self.focus = False

    def __str__(self):
        if self.focus:
            i = self.width - 3
            return '[%s ]' % self.text.ljust(i)[-i:]
        else:
            i = self.width - 2
            return '[%s]' % self.text.ljust(i)[:i]

    def _get_cursor(self):
        return min(1 + len(self.text), self.width - 2)

    cursor = property(_get_cursor)
